package models;

import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.query.ResultSetFactory;
import com.hp.hpl.jena.query.ResultSetFormatter;
import com.hp.hpl.jena.query.ResultSetRewindable;
import com.hp.hpl.jena.rdf.model.InfModel;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.util.FileManager;
//import com.sun.tools.javac.code.Attribute.Array;

import play.cache.Cache;
import play.db.jpa.Model;

import java.util.*;

public class Global extends Model{
	
	static String queryPrefixes = 
			"PREFIX dbpedia: <http://dbpedia.org/resource/>"+
			"PREFIX dbp-ont: <http://dbpedia.org/ontology/>"+
			"PREFIX owl: <http://www.w3.org/2002/07/owl#>"+
	        "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>"+
	        "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>"+
	        "PREFIX dc-term: <http://purl.org/dc/terms/>"+
	        "PREFIX foaf: <http://xmlns.com/foaf/0.1/>"+
	        "PREFIX geo: <http://www.w3.org/2003/01/geo/wgs84_pos#>"+
	        "PREFIX skos: <http://www.w3.org/2004/02/skos/core#>";
	
	/**
	 * Method that returns a model with all the local RDF resources loaded
	 */
	private static com.hp.hpl.jena.rdf.model.Model loadDataSources(){
		com.hp.hpl.jena.rdf.model.Model myRawModel = (com.hp.hpl.jena.rdf.model.Model) FileManager.get().loadModel("dbpedia/yago_links_short.nt", "N3");
		
		((com.hp.hpl.jena.rdf.model.Model) myRawModel).add(FileManager.get().loadModel("dbpedia/geo_coordinates_en_short.nt", "N3"));
		((com.hp.hpl.jena.rdf.model.Model) myRawModel).add(FileManager.get().loadModel("dbpedia/article_categories_en_short.nt", "N3"));
		((com.hp.hpl.jena.rdf.model.Model) myRawModel).add(FileManager.get().loadModel("dbpedia/geo_coordinates_en_short.nt", "N3"));
		((com.hp.hpl.jena.rdf.model.Model) myRawModel).add(FileManager.get().loadModel("dbpedia/skos_categories_en_short.nt", "N3"));
		((com.hp.hpl.jena.rdf.model.Model) myRawModel).add(FileManager.get().loadModel("dbpedia/instance_types_en_short.nt", "N3"));
		((com.hp.hpl.jena.rdf.model.Model) myRawModel).add(FileManager.get().loadModel("dbpedia/labels_en_short.nt", "N3"));
		((com.hp.hpl.jena.rdf.model.Model) myRawModel).add(FileManager.get().loadModel("dbpedia/long_abstracts_en_short.nt", "N3"));
		((com.hp.hpl.jena.rdf.model.Model) myRawModel).add(FileManager.get().loadModel("dbpedia/images_en-1_short.nt", "N3"));
		
		return myRawModel;
	}
	
	/**
	 * searchItems: searches items according to the input string of the user and the geographic coordinates
	 * @param searchTerm the string the user is searching from.
	 * @param minLat the minimum latitude for the area to search in
	 * @param maxLat the maximum latitude for the area to search in
	 * @param minLong the minimum longitude for the area to search in
	 * @param maxLong the maximum longitude for the area to search in
	 * @return
	 */
	public static List<Item> searchItems (String searchTerm, Double minLat, Double maxLat, Double minLong, Double maxLong){
		String distanceFilter="";
		if (minLat!=null){
			distanceFilter= "FILTER(?lat > "+ minLat + ")"+
						"FILTER(?lat < "+ maxLat + ")"+
						"FILTER(?long > "+ minLong + ")"+
						"FILTER(?long < "+ maxLong + ")";
		}
		
		String queryString=
				queryPrefixes + 
				"SELECT DISTINCT ?monumentUri ?label ?abstract ?lat ?long ?category ?thumb "+
		        "WHERE {"+
		        		"?monumentUri rdfs:label ?label. "+
		        		"?monumentUri dbp-ont:abstract ?abstract ."+
		        		"?monumentUri dbp-ont:thumbnail ?thumb ."+
		        	    "?monumentUri geo:lat ?lat ."+
		        	    "?monumentUri geo:long ?long ."+
		        	    "?monumentUri dc-term:subject ?category .";
		        	
					String[] testString = searchTerm.split(" ");
			        for (int i = 0; i < testString.length; i++) {
			        	queryString += "FILTER  ( regex(str(?abstract), \"" + testString[i] + "\", \"i\")  ||  regex(str(?category), \"" + testString[i] + "\", \"i\") )";

			        }
			        queryString += distanceFilter;
			        queryString += "} LIMIT 500";
			        
			        /**
			        /*The following code sniped would be used in the online version of the application
			        *
			        "SELECT DISTINCT ?monumentUri ?label ?abstract ?lat ?long ?category ?thumb "+
			        "WHERE {"+
			        	"{"+
			        		"?subtypes skos:broader <http://dbpedia.org/resource/Category:Cathedrals_in_Germany>. "+
			        		"?monumentUri dc-term:subject <http://dbpedia.org/resource/Category:Cathedrals_in_Germany>. "+
			        		"?monumentUri rdfs:label ?label. "+
			        		"?monumentUri dbp-ont:abstract ?abstract ."+
			        		"?monumentUri dbp-ont:thumbnail ?thumb ."+
			        	    "?monumentUri geo:lat ?lat ."+
			        	    "?monumentUri geo:long ?long ."+
			        	    "?monumentUri dc-term:subject ?category .";
			        	"}"+
			        	"UNION "+
			        	"{"+
			        		"?monumentUri rdf:type <http://dbpedia.org/class/yago/BasilicaChurchesInGermany>. "+
			        		"?monumentUri rdfs:label ?label. "+
			        		"?monumentUri dbp-ont:abstract ?abstract ."+
			        		"?monumentUri owl:thumbnail ?thumb ."+
			        	    "?monumentUri <http://www.w3.org/2003/01/geo/wgs84_pos#lat> ?lat ."+
			        	    "?monumentUri <http://www.w3.org/2003/01/geo/wgs84_pos#long> ?long ."+
			        	    "?monumentUri rdf:type ?type ."+
			        	    "?monumentUri dc-term:subject ?category ."+
			        	"}"+
				        "FILTER langMatches(lang(?abstract), 'en')"+
				        "FILTER langMatches(lang(?label), 'en')"+
				        String[] testString = searchTerm.split(" ");
				        for (int i = 0; i < testString.length; i++) {
				        	queryString += "FILTER  ( regex(str(?abstract), \"" + testString[i] + "\", \"i\")  ||  regex(str(?category), \"" + testString[i] + "\", \"i\") )";

				        }
				        queryString += distanceFilter;
				        queryString += "} LIMIT 500";
				        */
		
		/**
		 * Create the list that will be used to return all the items found
		 */
		List<Item> itemList =  new ArrayList<Item>();
		
		/**
		 * Instantiation of the needed objects to perform the data query
		 */
 		com.hp.hpl.jena.rdf.model.Model myRawModel = loadDataSources();
		InfModel infmodel = ModelFactory.createRDFSModel((com.hp.hpl.jena.rdf.model.Model) myRawModel);
		Query query = QueryFactory.create(queryString);
		QueryExecution qe = QueryExecutionFactory.create(query, infmodel);
		ResultSetRewindable results = ResultSetFactory.makeRewindable(qe.execSelect()) ;
		
		/**
		 * The following lines would be used in the online version of the application
		 * 
		 * QueryExecution qe = QueryExecutionFactory.sparqlService("http://dbpedia.org/sparql", query);
		 * ResultSet results = qe.execSelect();
		 */
		
		QuerySolution qs;
		String uri;
		
		HashMap<String,Item> itemsHM = new HashMap();
		Item item = null;
		
		List<String> uriList = new ArrayList();
		
		//Store item uris
		while (results.hasNext()){
			qs=results.next();
			uriList.add(qs.get("monumentUri").toString());		
		}
		
		results.reset();
		
		//Create items and save elements of the first query
		Iterator uriIt = uriList.iterator(); 
		while (uriIt.hasNext()){
			uri = uriIt.next().toString();
			item = saveSupportItems(uri, results);
			results.reset();
			if (item!=null && !itemList.contains(item)){
				itemsHM.put(item.uri, item);
				itemList.add(item);
			}
		}
		
		HashMap<Integer, String> attributesAdded = new HashMap();
		String attributeContent;
		
		/**
		 * Get the created items and add the attributes of the second query
		 * Only for online version in order to prevent SPARQL endpoint from
		 *running out of memory
		
		while (results2.hasNext()){
			qs = results2.next();
			if (qs.get("monumentUri")!=null && itemsHM.containsKey(qs.get("monumentUri").toString())){
				uri = qs.get("monumentUri").toString();
				attributeContent = qs.get("thumb").toString();
				if (!attributesAdded.containsKey((uri+attributeContent).hashCode())){
					item = itemsHM.get(uri);
					item.attributeList.add(new Attribute(item, "thumb", attributeContent));
					item.save();
					Cache.set(sessionId+item.uri, item);
					attributesAdded.put((uri+attributeContent).hashCode(), "foo");
				}
			}
		}*/
		return itemList;
	}
	
	/**
	 * saveSupportItems creates and saves the items with its attributes
	 * @param uri the uri of the item for which the attributes have to be saved
	 * @param results the results of the query
	 * @return the item with the associated attributes
	 */
	private static Item saveSupportItems(String uri, ResultSetRewindable results) {
		List<String> foundAttributes = new ArrayList<String>();

		HashMap<String, String> attrsNotToBeSaved = new HashMap();
		attrsNotToBeSaved.put("http://www.opengis.net/gml/_Feature", "foo");
		attrsNotToBeSaved.put("http://www.w3.org/2002/07/owl#Thing", "foo");
		attrsNotToBeSaved.put("http://dbpedia.org/ontology/Building", "foo");
		attrsNotToBeSaved.put("http://umbel.org/umbel/rc/Location_Underspecified", "foo");
		attrsNotToBeSaved.put("http://schema.org/Place", "foo");
		attrsNotToBeSaved.put("http://dbpedia.org/ontology/ArchitecturalStructure", "foo");
		attrsNotToBeSaved.put("http://dbpedia.org/ontology/Place", "foo");
		
		List<Attribute> attributeList = new ArrayList<Attribute>();
		
		QuerySolution qs;
		
		Item item = null;
		Attribute attrAux = null;
		//If there is no Item with uri = uri, create it and save its attributes
		if (Item.find("byUri", uri).fetch(1).isEmpty()){
			item = new Item(uri, true);		
			//Save single attributes
			while (results.hasNext()){
				attributeList = new ArrayList<Attribute>();
				qs = results.next(); 
				
				if (qs.get("monumentUri").toString().equals(uri)){
					attrAux = new Attribute(item, "abstract", qs.get("abstract").toString());
					attributeList.add(attrAux);
					attrAux = new Attribute(item, "label", qs.get("label").toString());
					attributeList.add(attrAux);
					attrAux = new Attribute(item, "lat", qs.get("lat").toString().substring(0,qs.get("lat").toString().lastIndexOf("^^")));
					attributeList.add(attrAux);
					attrAux = new Attribute(item, "long", qs.get("long").toString().substring(0,qs.get("long").toString().lastIndexOf("^^")));
					attributeList.add(attrAux);
					attrAux = new Attribute(item, "thumb", qs.get("thumb").toString());
					attributeList.add(attrAux);
					item.attributeList.addAll(attributeList);
					break;
				}
			}
			
			results.reset();
			attributeList = new ArrayList<Attribute>();
			
			//Save multiple attributes (type, category, ...)
			while (results.hasNext()){
				qs = results.next(); 

				List attributeType = new ArrayList(Arrays.asList("category"));
				if (qs.get("monumentUri").toString().equals(uri)){
					Iterator attrTypeIt = attributeType.iterator();
					while (attrTypeIt.hasNext()){
						String attribute = attrTypeIt.next().toString();
						if (!foundAttributes.contains(qs.get(attribute).toString())&&!attrsNotToBeSaved.containsKey(qs.get(attribute).toString())){
							foundAttributes.add(qs.get(attribute).toString());
							attributeList.add(new Attribute(item, attribute, qs.get(attribute).toString()));
						}
					}
				}
			}
			
			item.attributeList.addAll(attributeList);
			item.save();
		}else{
			item = Item.find("byUri", uri).first();
		}
		return item;
	}
	
	/** getItemDetails gets from the SPARQL endpoint the attributes needed
	 * in order to show an item's details
	 * @param uri uri of the item which's details have to be searched
	 */
	public static void getItemDetails (String uri){
		String queryString =			
				
		"PREFIX dbpedia: <http://dbpedia.org/resource/>"+
		"PREFIX dbp-ont: <http://dbpedia.org/ontology/>"+
        "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>"+
        "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>"+
        "PREFIX dc-term: <http://purl.org/dc/terms/>"+
        "PREFIX foaf: <http://xmlns.com/foaf/0.1/>"+
        "PREFIX geo: <http://www.w3.org/2003/01/geo/wgs84_pos#>"+
        "SELECT DISTINCT ?label ?abstract ?category ?type ?lat ?long ?thumb "+
        "WHERE {"+
        "<"+ uri +"> rdfs:label ?label ."+
        "<"+ uri +"> dbp-ont:abstract ?abstract ."+
        "<"+ uri +"> dc-term:subject ?category ."+
        "<"+ uri +"> rdf:type ?type ."+
        "<"+ uri +"> geo:lat ?lat ."+
        "<"+ uri +"> geo:long ?long ."+
        "<"+ uri +"> <http://dbpedia.org/ontology/thumbnail> ?thumb ."+
        "FILTER langMatches(lang(?abstract), 'en')"+
        "FILTER langMatches(lang(?label), 'en')"+
      "} LIMIT 100";

		Query query = QueryFactory.create(queryString);
				
		com.hp.hpl.jena.rdf.model.Model myRawModel = loadDataSources();
		InfModel infmodel = ModelFactory.createRDFSModel((com.hp.hpl.jena.rdf.model.Model) myRawModel);
		
		QueryExecution qe = QueryExecutionFactory.create(query, infmodel);
		
		saveItemDetailsAttributes(uri, qe);
	}
	
	/**
	 * saveItemDetailsAttributes: save the attributes gotten in getItemDetails
	 * @param uri uri of the item for which the attributes have to be saved
	 * @param qe the query to execute
	 */
	private static void saveItemDetailsAttributes(String uri, QueryExecution qe) {
		ResultSet results = qe.execSelect();
		List<Attribute> attributeList = new ArrayList<Attribute>();
		
		QuerySolution qs;
		
		if (Item.find("byUri", uri).fetch(1).isEmpty()){
			Item item = new Item(uri, false);
					
			//Save single attributes
			if (results.hasNext()){
				item.save();
				qs = results.next(); 
				
				attributeList.add(new Attribute(item, "label", qs.get("label").toString()));
				attributeList.add(new Attribute(item, "abstract", qs.get("abstract").toString()));
				attributeList.add(new Attribute(item, "lat", qs.get("lat").toString().substring(0,qs.get("lat").toString().lastIndexOf("^^"))));
				attributeList.add(new Attribute(item, "long", qs.get("long").toString().substring(0,qs.get("long").toString().lastIndexOf("^^"))));
				attributeList.add(new Attribute(item, "thumb", qs.get("thumb").toString()));
			}
					
			//Save "category" attributes
			results = qe.execSelect();
			attributeList.addAll(saveSpecificAttribute(item, "category", results));
			
			//Save "type" attributes
			results = qe.execSelect();
			attributeList.addAll(saveSpecificAttribute(item, "type", results));
			
			item.attributeList.addAll(attributeList);
			item.save();
		}
	}

	/**
	 * saveSpecificAttribute: selects specific attributes from the results
	 * and saves them.
	 * @param item the item for which the attributes have to be saved
	 * @param attributeName the attribute that has to be saved
	 * @param results the results of the query
	 * @return returns an attribute list
	 */
	private static List <Attribute> saveSpecificAttribute(Item item, String attributeName, ResultSet results){	
		List <Attribute> attributeList = new ArrayList();
		QuerySolution qs;
		Attribute auxAttr;
		List <String> auxContent = new ArrayList();
		while (results.hasNext()){
			qs = results.next();
			auxAttr = new Attribute(item, attributeName, qs.get(attributeName).toString());
			if (!auxContent.contains(auxAttr.content)){
				auxContent.add(auxAttr.content);
				attributeList.add(auxAttr);
			}
		}	
		return attributeList;
	}

	/**
	 * buildQueryFromEnabledAttributes: build the query to find the items
	 * according to the enabled search attributes
	 */
	public static void buildQueryFromEnabledAttributes (){
		// Look for the type and content of all the enabled Attributes
		// discarding the duplicates
				
		List<Object[]> enabledAttrList = Attribute.find("select distinct id, name, content from Attribute where enabled=1").fetch();
		String querySelect="SELECT DISTINCT ?monumentUri "+ /*?label ?abstract ?lat ?long*/ "WHERE {\n";
		String queryBody="";

		// Query BODY
		for (int i=0; i<enabledAttrList.size(); i++){
			Object[] retrievedAttr = enabledAttrList.get(i);
			String name = retrievedAttr[1].toString();
			String content = retrievedAttr[2].toString();
			if (name.equals("type"))
				queryBody = queryBody + "?monumentUri rdf:type <"+content+"> .\n";
		}

		if (queryBody!="") {
			queryBody = queryBody + "\n?monumentUri <http://dbpedia.org/ontology/thumbnail> ?thumb .";
			queryBody = queryBody + "} LIMIT 100";		
		
			// Join query parts
			String queryString = queryPrefixes + querySelect + queryBody;
			
			com.hp.hpl.jena.rdf.model.Model myRawModel = loadDataSources();
			InfModel infmodel = ModelFactory.createRDFSModel((com.hp.hpl.jena.rdf.model.Model) myRawModel);
			
			Query query = QueryFactory.create(queryString);
			QueryExecution qe = QueryExecutionFactory.create(query, infmodel);

			ResultSet results = qe.execSelect();
			
			QuerySolution qs;
			String uri;
			
			/**
			 * The hasmap would be used in theh online version
			 *
			 * Since we have to divide the queries, we need
			 * to know which items have been already stored
			 * when iterating throug the different queries results
			 *
 			 * HashMap<String,Item> itemsHM = new HashMap();
			 * Item item;
			 */
			
			//Create items and save elements of the first query
			if (!results.hasNext()) System.out.println("There are no results"); 
			while (results.hasNext()){
				qs = results.next();
				if (qs.get("monumentUri")!=null){
					uri = qs.get("monumentUri").toString();
					//Save attributes and return the created Item (only online version)
					//item = saveFoundItems(uri, qe);
					//if (item!=null) itemsHM.put(item.uri, item);
				}
			}
		}
	}

	/**
	 * saveFoundItems: save the items obtained with the buildQueryFromEnabledAttributes method
	 * 
	 * @param uri uri of the item for which the attributes have to be saved
	 * @param qe the query to execute
	 * @return
	 */
	private static Item saveFoundItems(String uri, QueryExecution qe) {
		ResultSet results = qe.execSelect();
		List<String> foundAttributes = new ArrayList<String>();
		List<Attribute> attributeList = new ArrayList<Attribute>();
		
		QuerySolution qs;
		
		Item item = null;
		
		//If there is no Item with uri = uri, create it and save its attributes
		if (Item.find("byUri", uri).fetch(1).isEmpty()){
			item = new Item(uri, false);		
			//Save single attributes
			while (results.hasNext()){
				attributeList = new ArrayList<Attribute>();
				qs = results.next(); 
				
				if (qs.get("monumentUri").toString().equals(uri)){
					attributeList.add(new Attribute(item, "label", qs.get("label").toString()));
					attributeList.add(new Attribute(item, "abstract", qs.get("abstract").toString()));
					attributeList.add(new Attribute(item, "lat", qs.get("lat").toString().substring(0,qs.get("lat").toString().lastIndexOf("^^"))));
					attributeList.add(new Attribute(item, "long", qs.get("long").toString().substring(0,qs.get("long").toString().lastIndexOf("^^"))));
					
					item.attributeList.addAll(attributeList);
					break;
				}
			}
			
			results = qe.execSelect();
			attributeList = new ArrayList<Attribute>();
			
			//Save multivalue attributes (type, category, ...)
			while (results.hasNext()){
				qs = results.next(); 
				//attributeType contains a list with the type of multivalue-attributes that have to be saved
				List attributeType = new ArrayList(Arrays.asList("type","category"));
				if (qs.get("monumentUri").toString().equals(uri)){
					Iterator attrTypeIt = attributeType.iterator();
					while (attrTypeIt.hasNext()){
						String attribute = attrTypeIt.next().toString();
						if (!foundAttributes.contains(qs.get(attribute).toString())){
							foundAttributes.add(qs.get(attribute).toString());
							attributeList.add(new Attribute(item, attribute, qs.get(attribute).toString()));
						}
					}
				}
			}
			
			item.attributeList.addAll(attributeList);
			item.save();
		}
		return item;
	}
}
